/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package vrp;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

/**
 * Class permitting to interact with external files
 * Load problems, write solutions ...
 * @author pmartin
 */
public class FilesManager {
    
	/**
	 * Parse an instance file to create a new problem.
	 * The problem is returned as a Customer Map.
	 * @param filePath Path of the source file
	 * @return Map of customers representing the problem. Key = customer id, Value = customer object 
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
    public Map<Integer,Customer> parseInstanceFile(String filePath) throws FileNotFoundException, IOException{
        Map<Integer,Customer> resu = new HashMap<Integer,Customer>();
        Customer currCustomer;
        String line;
        // Skip the first 5 lines
        BufferedReader br = new BufferedReader(new FileReader(filePath));
        for(int i=0;i<5;i++) br.readLine();
        // Gets the capacity (CAPACITY : value)
        line = br.readLine();
        Constants.carCapacity = new Integer(line.substring(11));
        // Read customers coords
        line = br.readLine(); // SkipLine "NODE_COORD_SECTION"
        String[] parts;
        line = br.readLine();
        while(!"DEMAND_SECTION".equals(line)){
            currCustomer = new Customer();
            parts = line.split(" ");
            
            currCustomer.setId(new Integer(parts[0]));
            currCustomer.setX(new Integer(parts[1]));
            currCustomer.setY(new Integer(parts[2]));
            
            resu.put(currCustomer.getId(), currCustomer);
            line = br.readLine();
        }
        Constants.numCustomers = new Integer(resu.size());
        // Read customers requirements
        line = br.readLine();
        while(!"DEPOT_SECTION".equals(line)){
            parts = line.split(" ");
            // We set the requirement for the customer whose id is part[0]
            resu.get(new Integer(parts[0])).setReq(new Integer(parts[1]));
            line = br.readLine();
        }
        // Read depot
        // NOTE : /!\ There is a space before the customer id (e.g : " 1")
        line = br.readLine().trim();
        currCustomer = resu.get(new Integer(line));
        currCustomer.setIsDepot(true);
        Constants.depot = currCustomer;
        return resu;
    }
    
    /**
     * Write all the output files in the specified format from the given datas
     * @param finalSolution Solution found by the algorithm
     * @param tExec Total algorithm execution time in seconds
     * @param objVal List of each iteration's current solution's length
     * @param bobjVal List of each iteration's current best solution's length
     * @throws IOException If there is a problem while we write one of the files
     */
    public void writeOutputFiles(Solution finalSolution, double tExec, LinkedList<Double> objVal, LinkedList<Double> bobjVal) throws IOException{
    	this.writeSolutionFile(finalSolution, "solution.res");
    	// Write "obj.res" with each iteration's current solution's length
    	this.writeSequenceGnuplotFile(objVal, "obj.res");
    	// Write "bobj.res" with each iteration's best solution's length
    	this.writeSequenceGnuplotFile(bobjVal, "bobj.res");
    	// Writing the execution time in a file
    	BufferedWriter bw = new BufferedWriter(new FileWriter("temps.res"));
    	bw.write(new Double(tExec).toString());
    	bw.close();
    }
    
    /**
     * Write all the debug files in the specified format from the given datas
     * @throws IOException 
     */
    public void writeDebugFiles(LinkedList<Double> alphaVal, LinkedList<Double> qVal) throws IOException{
    	this.writeSequenceGnuplotFile(alphaVal, "alpha.res");
    	this.writeSequenceGnuplotFile(qVal, "q.res");
    }
    
    /**
     * Write a sequence of values in a compatible gnuplot datafile.
     * Format :
     * 	# X Y
     * 	  i val
     * @param objVal Values to write in the file
     * @param filename Path of the output file - The file will be truncated
     * @throws IOException 
     */
    private void writeSequenceGnuplotFile(LinkedList<Double> objVal, String filename) throws IOException {
    	BufferedWriter bw = new BufferedWriter(new FileWriter(filename));
    	
    	bw.write("# Iteration Length");
    	bw.newLine();
    	
    	Integer i = new Integer(1);
    	for(Double d : objVal){
        	bw.write("  "+i.toString()+" "+d.toString());
        	bw.newLine();
        	i = new Integer(i.intValue() + 1);
    	}
    	
    	bw.close();
	}

	/**
     * Write the given solution in a file with the following format :
     * 	Solution length (2 decimals)
     * 	Number of rounds
     * 	For each round : Ordered by ascending first visited customer id
     * 		Round length (2 decimals) - Capacity (usage) - Number of customers - Visited customers ordered by their visit order 
     * @param sol Solution to write in the output file
     * @param filename Path of the output file - The file will be truncated
     * @throws IOException If there is a problem while we write in the given file
     */
    private void writeSolutionFile(Solution sol, String filename) throws IOException{
    	BufferedWriter bw = new BufferedWriter(new FileWriter(filename));
    	
    	bw.write(new BigDecimal(sol.getCost()).setScale(2, BigDecimal.ROUND_HALF_UP).toString());
    	bw.newLine();
    	bw.write(new Integer(sol.getNumVehicles()).toString());
    	bw.newLine();
    	
    	// Construct and sort the round list
    	LinkedList<Vehicle> vehicles = new LinkedList<Vehicle>();
    	Vehicle currVehicle = sol.getFirstVehicle();
    	while(currVehicle != null){
    		vehicles.add(currVehicle);
    		currVehicle = currVehicle.getNextCar();
    	}
    	Collections.sort(vehicles);
    	// Each round is now ordered so we can output the wanted datas
    	LinkedList<Customer> customers;
    	Customer currCustomer;
    	for(Vehicle v : vehicles){
    		bw.write(new BigDecimal(v.getLength()).setScale(2, BigDecimal.ROUND_HALF_UP).toString()+" ");
        	bw.write(new Integer(v.getUsage()).toString()+" ");
        	// Construct the visited customers list
        	customers = new LinkedList<Customer>();
        	currCustomer = v.getFirstCustomer();
        	while(currCustomer != Constants.depot){
        		customers.add(currCustomer);
        		currCustomer = currCustomer.getNextCustomer();
        	}
        	// And now, write customers related datas
        	bw.write(new Integer(customers.size()).toString()+" ");
        	for(Customer c : customers){
        		bw.write(new Integer(c.getId()).toString()+" ");
        	}
    		bw.newLine();
    	}
    	
    	bw.close();
    }

    /**
     * Read the given solution file and populate a "Solution object". 
     * Customers are linked to the solution thanks to their id.
     * The file must have the following format :
     * 	Solution length (2 decimals)
     * 	Number of rounds
     * 	For each round : Ordered by ascending first visited customer id
     * 		Round length (2 decimals) - Capacity (usage) - Number of customers - Visited customers ordered by their visit order 
     * @param solutionFile Name of the file containing the solution to read
     * @param customers Map of customers keyed by id
     * @return Solution corresponding to the file
     * @throws IOException 
     * @throws NumberFormatException 
     */
	public Solution createSolutionFromFile(String solutionFile, Map<Integer, Customer> customers) throws NumberFormatException, IOException {
		Solution resu = new Solution();
		BufferedReader br = new BufferedReader(new FileReader(solutionFile));
    	
		resu.setCost(new Double(br.readLine()));
		int numVehicles = new Integer(br.readLine());
		resu.setNumVehicles(numVehicles);
    	
    	// Each round is listed so we can reconstruct them
		Vehicle currVehicle = new Vehicle();
		String[] currVehicleValues;
		int numCustomers, iCurrValue;
    	for(int i=0; i<numVehicles; i++){
    		if(i==0){
    			resu.setFirstVehicle(currVehicle);
    		} else {
    			currVehicle.setNextCar(new Vehicle());
    			currVehicle.getNextCar().setPrevCar(currVehicle);
    			currVehicle = currVehicle.getNextCar();
    		}
    		currVehicleValues = br.readLine().split(" ");
    		iCurrValue = 0;
    		
    		currVehicle.setId(i+1);
    		// We do not populate the usage and length because it is automatically calculated when a customer is added
    		// currVehicle.setLength(new Double(currVehicleValues[iCurrValue++]));
        	// currVehicle.setUsage(new Integer(currVehicleValues[iCurrValue++]));
    		currVehicle.setLength(0.0); iCurrValue++;
    		currVehicle.setUsage(0); iCurrValue++;
        	// Reconstruct the visited customers order and insert the corresponding customer object in the vehicle round
        	numCustomers = new Integer(currVehicleValues[iCurrValue++]);
        	numCustomers += iCurrValue-1; // numCustomers will now contains the last customer id index in the values array 
        	while(iCurrValue <= numCustomers){
    			currVehicle.addLastCustomer(customers.get(new Integer(currVehicleValues[iCurrValue++])));
        	}
    	}
    	
    	br.close();
    	return resu;
	}

	/**
	 * Write the current solution length, each round length and usage in the given file.
	 * File format :
	 * 	Total solution length
	 * 	For each round :
	 * 		length 	usage
	 * @param filename Name of the file in which we have to write the values 
	 * @param sol Solution containing all the elements to write
	 * @throws IOException 
	 */
	public void writeFSolutionFile(String filename, Solution sol) throws IOException {
		BufferedWriter bw = new BufferedWriter(new FileWriter(filename));
    	
    	bw.write(new BigDecimal(sol.getLength()).setScale(2, BigDecimal.ROUND_HALF_UP).toString());
    	bw.newLine();
    	
    	// Construct and sort the round list
    	LinkedList<Vehicle> vehicles = new LinkedList<Vehicle>();
    	Vehicle currVehicle = sol.getFirstVehicle();
    	while(currVehicle != null){
    		vehicles.add(currVehicle);
    		currVehicle = currVehicle.getNextCar();
    	}
    	Collections.sort(vehicles);
    	// Each round is now ordered so we can output the wanted datas
    	for(Vehicle v : vehicles){
    		bw.write(new BigDecimal(v.getLength()).setScale(2, BigDecimal.ROUND_HALF_UP).toString()+" ");
        	bw.write(new Integer(v.getUsage()).toString()+" ");
    		bw.newLine();
    	}
    	
    	bw.close();
	}
}